﻿using Dapper_Core.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Linq;
using System.Xml.Linq;


namespace Dapper_Core.Common
{
    public class WechatPayUtility
    {
        //微信支付地址
        private static string wechat_postUrl { get { return "https://api.mch.weixin.qq.com"; } }

        /// <summary>
        /// 微信H5支付
        /// </summary>
        /// <param name="out_trade_no"></param>
        /// <param name="amount"></param>
        /// <param name="body"></param>
        /// <param name="spbill_create_ip"></param>
        /// <param name="openid"></param>
        /// <param name="trade_type"></param>
        /// <returns></returns>
        public static ResultBase<Result_WechatPayBack> WechatPay_unifiedorder(string out_trade_no, decimal amount, string body, string spbill_create_ip, string openid, string trade_type)
        {
            var result = new ResultBase<Result_WechatPayBack>();

            try
            {
                var nonce_str = CreateNonceStr();//生成随机字符串
                var total_fee = amount;//元为单位              
                var scene_info = $@"{{""store_info"": {{""id"":""BJJH001"",""name"": ""北京金和"",""area_code"": ""110000"",""address"": ""北京市海淀区""}}}}";
                var time_start = DateTime.Now.ToString("yyyyMMddHHmmss");
                var time_expire = DateTime.Now.AddHours(1).ToString("yyyyMMddHHmmss");

                var pre_order_httpResult = UnifiedOrder(Appsettings.WechatAppId, body, body, Appsettings.WechatMerchant, nonce_str, Appsettings.WechatNotifyUrl,
                    out_trade_no, spbill_create_ip, total_fee, trade_type, openid, scene_info, time_start, time_expire);

                //微信返回
                var pre_order_resultStr = XElement.Parse(pre_order_httpResult);
                var pre_order_result_code = pre_order_resultStr.Element("return_code").Value;
                var pre_order_result_msg = pre_order_resultStr.Element("return_msg").Value;
                if (pre_order_result_code == "SUCCESS")
                {
                    var pre_order_result_mweb_url = pre_order_resultStr.Element("mweb_url").Value;

                    return result.SuccessResult(new Result_WechatPayBack
                    {
                        FormBody = pre_order_result_mweb_url,
                        Out_trade_no = out_trade_no
                    });
                }
                else
                {
                    return result.ErrorResult("获取支付失败：" + pre_order_result_msg);
                }
            }
            catch (Exception ex)
            {
                return result.ErrorResult("异常：" + ex.ToString());
            }

        }

        /// <summary>
        /// 微信JSAPI支付
        /// </summary>
        /// <param name="out_trade_no">订单号</param>
        /// <param name="amount">金额元</param>
        /// <param name="body">WAP 网站名</param>
        /// <param name="spbill_create_ip">终端ip</param>
        /// <param name="openid">用户标识</param>
        /// <param name="trade_type">交易类型 trade_type=JSAPI时（即JSAPI支付），此参数必传</param>
        /// <returns></returns>
        public static ResultBase<Result_WechatPayBack> WechatPay_unifiedorder_JSAPI(string out_trade_no, decimal amount, string body, string spbill_create_ip, string openid, string trade_type)
        {
            var result = new ResultBase<Result_WechatPayBack>();

            try
            {
                var nonce_str = CreateNonceStr();//生成随机字符串
                var total_fee = amount;//元为单位
                var scene_info = $@"{{""store_info"": {{""id"":""BJJH001"",""name"": ""北京金和"",""area_code"": ""110000"",""address"": ""北京市海淀区""}}}}";
                var time_start = DateTime.Now.ToString("yyyyMMddHHmmss");
                var time_expire = DateTime.Now.AddHours(1).ToString("yyyyMMddHHmmss");


                var pre_order_httpResult = UnifiedOrder(Appsettings.WechatAppId, body, body, Appsettings.WechatMerchant, nonce_str, Appsettings.WechatNotifyUrl,
                    out_trade_no, spbill_create_ip, total_fee, trade_type, openid, scene_info, time_start, time_expire);

                NlogUtility.WriteInfo(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName, "WechatPayUtility.HttpPostRequestXml()执行出现异常", "pre_order_httpResult=" + pre_order_httpResult, "DapperCore");

                //微信返回
                var pre_order_resultStr = XElement.Parse(pre_order_httpResult);
                var pre_order_result_code = pre_order_resultStr.Element("return_code").Value;
                var pre_order_result_msg = pre_order_resultStr.Element("return_msg").Value;
                if (pre_order_result_code == "SUCCESS")
                {
                    var pre_order_result_prepay_id = pre_order_resultStr.Element("prepay_id").Value;

                    return result.SuccessResult(new Result_WechatPayBack
                    {
                        FormBody = pre_order_result_prepay_id,
                        Out_trade_no = out_trade_no
                    });
                }
                else
                {
                    return result.ErrorResult("获取支付失败：" + pre_order_result_msg);
                }
            }
            catch (Exception ex)
            {
                return result.ErrorResult("异常：" + ex.ToString());
            }

        }

        /// <summary>
        /// 下单
        /// </summary>
        /// <param name="appid">公众账号ID</param>
        /// <param name="attach">附加数据，在查询API和支付通知中原样返回，该字段主要用于商户携带订单的自定义数据</param>
        /// <param name="body">String(32) 商品描述 商品或支付单简要描</param>
        /// <param name="mch_id">商户号</param>
        /// <param name="nonce_str">随机字符串</param>
        /// <param name="notify_url">接收微信支付异步通知回调地址,不可带参,与下面的Notify对应，开发者可自定义其他url地址 </param>
        /// <param name="out_trade_no">商户系统内部的订单号,32个字符内、可包含字母</param>
        /// <param name="spbill_create_ip">终端ip</param>
        /// <param name="total_fee">收钱总额  分为单位 前台传过来后需要处理成分</param>
        /// <param name="trade_type">交易类型H5支付的交易类型为MWEB</param>
        /// <param name="openid">用户标识 trade_type=JSAPI时（即JSAPI支付），此参数必传</param>
        /// <param name="scene_info">场景信息 WAP网站应用{"h5_info": {"type":"Wap","wap_url": "https://pay.qq.com","wap_name": "腾讯充值"}}</param>
        /// <param name="time_start">交易起始时间</param>
        /// <param name="time_expire">交易结束时间</param>
        /// <returns></returns>
        private static string UnifiedOrder(string appid, string attach, string body, string mch_id, string nonce_str, string notify_url, string out_trade_no,
            string spbill_create_ip, decimal total_fee, string trade_type, string openid, string scene_info, string time_start, string time_expire)
        {
            var stringADict = new Dictionary<string, string>();
            stringADict.Add("appid", appid);
            stringADict.Add("attach", attach);
            stringADict.Add("body", body);
            stringADict.Add("mch_id", mch_id);
            stringADict.Add("nonce_str", nonce_str);
            stringADict.Add("notify_url", notify_url);
            stringADict.Add("out_trade_no", out_trade_no);
            stringADict.Add("spbill_create_ip", spbill_create_ip);
            stringADict.Add("total_fee", Math.Round(total_fee * 100, 0).ToString());//元转分
            stringADict.Add("trade_type", trade_type);
            if (!string.IsNullOrEmpty(openid))
            {
                stringADict.Add("openid", openid);
            }
            stringADict.Add("scene_info", scene_info);
            stringADict.Add("time_start", time_start);
            stringADict.Add("time_expire", time_expire);
            var sign = Sign(stringADict, Appsettings.WechatPartnerKey);//生成签名字符串  

            //组合xml内容
            StringBuilder strBuilder = new StringBuilder();
            strBuilder.Append("<xml>");
            strBuilder.Append($"<appid>{appid}</appid>");//公众号id  
            strBuilder.Append($"<attach>{attach}</attach>");//附加数据
            strBuilder.Append($"<body>{body}</body>");//商品描述
            strBuilder.Append($"<mch_id>{mch_id}</mch_id>");//商户号  
            strBuilder.Append($"<nonce_str>{nonce_str}</nonce_str>");//随机字符串  
            strBuilder.Append($"<notify_url>{notify_url}</notify_url>");//接收微信支付异步通知回调地址,不可带参,与下面的Notify对应，开发者可自定义其他url地址  
            strBuilder.Append($"<out_trade_no>{out_trade_no}</out_trade_no>");//商户系统内部的订单号,32个字符内、可包含字母  
            strBuilder.Append($"<spbill_create_ip>{spbill_create_ip}</spbill_create_ip>");//终端ip  
            strBuilder.Append($"<total_fee>{Math.Round(total_fee * 100, 0).ToString()}</total_fee>");//收钱总额  分为单位 前台传过来后需要处理成分  
            strBuilder.Append($"<trade_type>{trade_type}</trade_type>");//交易类型H5支付的交易类型为MWEB  
            if (!string.IsNullOrEmpty(openid))
            {
                strBuilder.Append($"<openid>{openid}</openid>");//用户标识
            }
            strBuilder.Append($"<scene_info>{scene_info}</scene_info>");
            strBuilder.Append($"<time_start>{time_start}</time_start>");//交易起始时间
            strBuilder.Append($"<time_expire>{time_expire}</time_expire>");//交易结束时间
            strBuilder.Append($"<sign>{sign}</sign>");
            strBuilder.Append("</xml>");

            //var url = _pre_order_url + "/sandboxnew/pay/unifiedorder";//沙箱
            var url = wechat_postUrl + "/pay/unifiedorder";
            var pre_order_httpResult = HttpPostRequestXml(url, strBuilder);
            return pre_order_httpResult;
        }

        /// <summary>
        /// 查询订单
        /// </summary>
        /// <param name="out_trade_no">商户系统内部的订单号或微信的订单号</param>
        /// <returns>0待支付 1成功 2失败</returns>
        public static int Wechat_Orderquery(string out_trade_no)
        {
            var nonce_str = CreateNonceStr();
            var stringADict = new Dictionary<string, string>();
            stringADict.Add("appid", Appsettings.WechatAppId);
            stringADict.Add("mch_id", Appsettings.WechatMerchant);
            stringADict.Add("out_trade_no", out_trade_no);
            stringADict.Add("nonce_str", nonce_str);
            var sign = Sign(stringADict, Appsettings.WechatPartnerKey);//生成签名字符串  

            //组合xml内容
            StringBuilder strBuilder = new StringBuilder();
            strBuilder.Append("<xml>");
            strBuilder.Append($"<appid>{Appsettings.WechatAppId}</appid>");
            strBuilder.Append($"<mch_id>{Appsettings.WechatMerchant}</mch_id>");
            strBuilder.Append($"<out_trade_no>{out_trade_no}</out_trade_no>");
            strBuilder.Append($"<nonce_str>{nonce_str}</nonce_str>");
            strBuilder.Append($"<sign>{sign}</sign>");
            strBuilder.Append("</xml>");

            var url = wechat_postUrl + "/pay/orderquery";
            var pre_order_httpResult = HttpPostRequestXml(url, strBuilder);
            var pre_order_resultStr = XElement.Parse(pre_order_httpResult);
            var pre_order_result_code = pre_order_resultStr.Element("return_code").Value;//获取请求状态
            var pre_order_result_msg = pre_order_resultStr.Element("return_msg").Value;//获取提示信息
            if (pre_order_result_code == "SUCCESS")
            {
                var pre_order_result_trade_state = pre_order_resultStr.Element("trade_state").Value;//获取支付状态
                if (pre_order_result_trade_state == "NOTPAY")
                {
                    return 0;
                }
                else if (pre_order_result_trade_state == "SUCCESS")
                {
                    return 1;
                }
                else
                {
                    return 2;
                }
            }
            else
            {
                return 0;
            }
        }

        /// <summary>  
        /// 创建随机字符串  
        /// </summary>  
        /// <returns></returns>  
        private static string CreateNonceStr()
        {
            string[] strs = new string[]
                                  {
                                  "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
                                  "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"
                                  };

            Random r = new Random();
            var sb = new StringBuilder();
            var length = strs.Length;
            for (int i = 0; i < 15; i++)
            {
                sb.Append(strs[r.Next(length - 1)]);
            }
            return sb.ToString();
        }

        /// <summary>
        /// 发送post xml文件请求
        /// </summary>
        /// <param name="Url"></param>
        /// <param name="strBuilder"></param>
        /// <returns></returns>
        private static string HttpPostRequestXml(string Url, StringBuilder strBuilder)
        {
            string result = string.Empty;
            string data = strBuilder.ToString();
            //进行utf-8编码
            var encoding = Encoding.GetEncoding("utf-8");
            byte[] buffer = encoding.GetBytes(data);
            //根据webURL创建HttpWebRequest对象
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url);
            request.Method = "post";
            request.ContentLength = buffer.Length;
            request.ContentType = "text/xml";
            NlogUtility.WriteInfo(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName, "WechatPayUtility.HttpPostRequestXml()进行支付调用", "Url=" + Url + "，strBuilder=" + strBuilder.ToString(), "DapperCore");
            StreamWriter myWriter = null;
            try
            {
                myWriter = new StreamWriter(request.GetRequestStream());
                myWriter.Write(data);
            }
            catch (Exception ex)
            {
                NlogUtility.WriteError(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName, "WechatPayUtility.HttpPostRequestXml()执行出现异常", "Url=" + Url + "，strBuilder=" + strBuilder.ToString(), ex, "DapperCore");
                Console.WriteLine(ex.Message);
            }
            finally
            {
                myWriter.Close();
            }
            //读取服务器返回的信息
            HttpWebResponse objResponse = (HttpWebResponse)request.GetResponse();
            using (StreamReader sr = new StreamReader(objResponse.GetResponseStream()))
            {
                result = sr.ReadToEnd();
            }
            return result;
        }

        /// <summary>  
        /// 生成签名  
        /// 签名在线验证工具：  
        /// http://mch.weixin.qq.com/wiki/tools/signverify/  
        /// </summary>  
        /// <param name="stringADict">参与签名生成的参数列表</param>  
        /// <param name="partnerKey">商家私钥</param>  
        /// <returns></returns>  
        private static string Sign(IDictionary<string, string> stringADict, string partnerKey)
        {
            var sb = new StringBuilder();
            foreach (var sA in stringADict.OrderBy(x => x.Key))//参数名ASCII码从小到大排序（字典序）
            {
                if (string.IsNullOrEmpty(sA.Value)) continue;//参数的值为空不参与签名；  
                if (string.Compare(sA.Key, "sign", true) == 0) continue;    // 参数中为签名的项，不参加计算  
                sb.Append(sA.Key).Append("=").Append(sA.Value).Append("&");
            }
            var string1 = sb.ToString();
            string1 = string1.Remove(string1.Length - 1, 1);
            sb.Append("key=").Append(partnerKey);//在stringA最后拼接上key=(API密钥的值)得到stringSignTemp字符串  
            var stringSignTemp = sb.ToString();
            var sign = MD5Encrypt(stringSignTemp, "UTF-8").ToUpper();//对stringSignTemp进行MD5运算，再将得到的字符串所有字符转换为大写，得到sign值signValue。   
            return sign;
        }

        /// <summary>
        /// Md5运算
        /// </summary>
        /// <param name="password"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        private static string MD5Encrypt(string password, string encoding)
        {
            MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
            byte[] hashedDataBytes;
            hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding(encoding).GetBytes(password));
            StringBuilder tmp = new StringBuilder();
            foreach (byte i in hashedDataBytes)
            {
                tmp.Append(i.ToString("x2"));
            }
            return tmp.ToString();
        }
    }
}
